#include <stdarg.h>
#include <string.h>

#define is_digit(c)	((c) >= '0' && (c) <= '9')

static int skip_atoi(const char **s)
{
	int i=0;

	while (is_digit(**s))
		i = i*10 + *((*s)++) - '0';
	return i;
}

#define ZEROPAD	1		/* pad with zero */
#define SIGN	2		/* unsigned/signed long */
#define PLUS	4		/* show plus */
#define SPACE	8		/* space if plus */
#define LEFT	16		/* left justified */
#define SPECIAL	32		/* 0x */
#define SMALL	64		/* use 'abcdef' instead of 'ABCDEF' */

/*
 * 除法过程
 * %eax = n
 * %edx = 0
 * divl base
 * 结果存放
 * n = %eax  // 商
 * __res = %edx // 余数
 */
#define do_div(n,base) ({ \
    int __res; \
    __asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base)); \
    __res; }) /* 返回余数 */

/*
 * 入参说明
 * str --- 输出缓冲区，进制转换后的字符存在这里
 * num --- 待转换的数
 * base --- 带转换数的进制，例如10 进制、8进制、16进制等
 * size --- 转换后的宽度
 * precision --- 转换后的显示精度
 * type --- 显示标志s
 */
static char * number(char * str, int num, int base, int size, int precision
	,int type)
{
	char c,sign,tmp[36];
	const char *digits="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	int i;

	if (type&SMALL) digits="0123456789abcdefghijklmnopqrstuvwxyz"; /* 小写 */
	if (type&LEFT) type &= ~ZEROPAD; /* 左对齐就不能直接在右边补0了 */
	if (base<2 || base>36) /* 只支持2进制到36进制 */
		return 0;
	c = (type & ZEROPAD) ? '0' : ' ' ; /* 看是补0还是补空格 */
	if (type&SIGN && num<0) { /* 有符号数，且是负数，则要添加负号 */
		sign='-';
		num = -num; /* 添加完负号，数字本身按照绝对值转换为字符串 */
	} else {
		/* 
		 * 如果要显示+号，符号为+
		 * 否则判断是否输出空格
		 * 都不满足，则没有符号位
		 */
		sign=(type&PLUS) ? '+' : ((type&SPACE) ? ' ' : 0);
	}
	if (sign) size--; /* 有符号为，则显示的宽度要预留出来 */
	if (type&SPECIAL) { /* 是否要按照 0x 方式输出 */
		if (base==16) {
            size -= 2; /* 16进制，添加前导 0x，需要留出2个字符 */
        } else if (base==8) {
            size--; /* 8进制，只需要添加前导0，因此预留1个字节位置 */
        }
    }
	i=0;
	if (num == 0) {
		tmp[i++]='0'; /* 数值为0，则显示字符0 */
	} else {
		while (num != 0) {
			/* 
			 * 非0数字，根据数字/进制得到的余数，去索引字符 
			 * 例如16进制数0x1F，do_div的结果为 0x1F / 16 = 1 ...... 15
			 * 因此索引得到的是字符 f 
			 * 有点巧妙! 
			*/
			tmp[i++]=digits[do_div(num,base)];
		}
	}
	if (i>precision) precision=i; /* 精度不能小于数字的位数 */
	size -= precision; /* 显示的总的宽度，去除掉精度后，再补齐 */
	if (!(type&(ZEROPAD+LEFT))) { /* 不是左对齐也不是补0，那么直接在左边输出中补充空格 */
		while(size-->0) {
			*str++ = ' '; /* 右对齐，在左边补空格 */
		}
	}
	if (sign)
		*str++ = sign; /* 显示符号位 */
	if (type&SPECIAL) {
		if (base==8) {
			*str++ = '0'; /* 8进制显示前导0 */
        } else if (base==16) {
			*str++ = '0';
			*str++ = digits[33]; /* 16进制显示前导 0x 或 0X */
		}
    }
	if (!(type&LEFT)) {
		while(size-->0) {
			*str++ = c; /* 右对齐，在左边补0的场景 */
		}
	}
	while(i<precision--)
		*str++ = '0'; /* 数字的长度小于精度，在左边补0 */
	while(i-->0)
		/* 
		 * tmp 得到的字符是余数序列，从低位到高位
		 * 所以要正常显示（也就是从高位到低位），要反序输出 
		 * */
		*str++ = tmp[i]; 
	while(size-->0)
		*str++ = ' '; /* 左对齐场景，最后是在右边补空格 */
	return str;
}

int vsprintf(char *buf, const char *fmt, va_list args)
{
	int len;
	int i;
	char * str;
	char *s;
	int *ip;

	int flags;		/* flags to number() */

	int field_width;	/* width of output field */
	int precision;		/* min. # of digits for integers; max
				   number of chars for from string */
	// int qualifier;		/* 'h', 'l', or 'L' for integer fields */

	/* 
	 * str指向输出
	 * fmt为格式字符串，当*fmt为0时候，也就是结束字符，则循环结束 
	 * 每次步进一个字符
	 */
	for (str=buf ; *fmt ; ++fmt) {
		if (*fmt != '%') { /* 不是% 则说明不需要进行格式化处理 */
			*str++ = *fmt; /* 那么直接拷贝到输出即可 */
			continue;
		}
			
		/* 开始处理格式化字符 */
		flags = 0;
		repeat:
			/* 跳过字符 %，不用拷贝到目标缓冲区 */
			++fmt;		/* this also skips first '%' */
			/* 开始匹配下一个字符 */
			switch (*fmt) {
				/*
				 * 标记位，每个bit位代表一中标记类型
				 * bit0 --- ZEROPAD，以0进行对齐
				 * bit1 --- SIGN，区分有符号和无符号
				 * bit2 --- PLUS，显示+号
				 * bit3 --- SPACE，如果是+号，则按照空格显示
				 * bit4 --- LEFT，左对齐
				 * bit5 --- SPECIAL，显示时加上前缀0x
				 * bit6 --- SMALL，使用小写'abcdef'替代'ABCDEF'
				 * 也就是在 % 后支持 下面 5 个特殊符号，直到匹配特殊字符结束
				 */
				case '-': flags |= LEFT; goto repeat;
				case '+': flags |= PLUS; goto repeat;
				case ' ': flags |= SPACE; goto repeat;
				case '#': flags |= SPECIAL; goto repeat;
				case '0': flags |= ZEROPAD; goto repeat;
				}
		
		/* get field width */
		/* 待处理域的宽度 */
		field_width = -1;
		if (is_digit(*fmt)) {
			/*
			 * 如果跟着的是数字，则直接使用数字的值作为宽度
			 * 由于已经处理过 - ，因此不会有负数值
			 */
			field_width = skip_atoi(&fmt);
		} else if (*fmt == '*') {
			/*
			 * 获取下一个参数的值作为宽度，并跳过4字节让args指向下一个参数 
			 * 如果是负数，则按照左对齐的方式，并把对齐结果转换成负数的绝对值
			 */
			field_width = va_arg(args, int);
			if (field_width < 0) {
				field_width = -field_width;
				flags |= LEFT;
			}
			/* 这里似乎没有对 fmt++, 感觉有点问题，待测试一下 */
			fmt++; /* 确实有问题，见用例 test/test_vsprintf.c:test_vsprintf_ex */
		}

		/* 开始处理精度 */
		precision = -1;
		if (*fmt == '.') {
			/* 精度以 . 号开始 （也是要跟在一个 % 的处理区间 之后）*/
			++fmt;	
			if (is_digit(*fmt))
				/* 如果是数字，则直接解析值作为精度 */
				precision = skip_atoi(&fmt);
			else if (*fmt == '*') {
				/* 如果是 * 则 使用 下一个参数作为精度值 */
				/* it's the next argument */
				precision = va_arg(args, int);
				/* 这里似乎没有对 fmt++, 感觉有点问题，待测试一下 */
				fmt++; /* 确实有问题，见用例 test/test_vsprintf.c:test_vsprintf_ex */
			}
			if (precision < 0)
				precision = 0; /* 精度至少为0 */
		}

		/* 获取支持的前缀模式，目前支持 h l L，实际上并未做任何处理 */
		// qualifier = -1;
		if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
			// qualifier = *fmt;
			++fmt;
		}

		/* 下面开始真正处理数据了 */
		switch (*fmt) {
		case 'c':
			/*
			 * 字符类型，单个字符 
			 */
			if (!(flags & LEFT))
				/* 如果是左对齐，则需要先左边填充*/
				while (--field_width > 0)
					*str++ = ' ';
			*str++ = (unsigned char) va_arg(args, int); /* 取出并转为字符 */
			/* 这里处理的是右对齐的情况（默认情况），则在字符后面继续填充空格 */
			while (--field_width > 0)
				*str++ = ' ';
			break;

		case 's':
			/* 
			 * 字符串类型，先计算长度
			 * 如果显示精度<0，则直接显示整个字符串
			 * 如果显示精度小于字符串长度，则只显示精度指示的长度
			 * 精度大于字符串长度，则显示整个字符串长度即可
			 */
			s = va_arg(args, char *);
			len = strlen(s);
			if (precision < 0)
				precision = len;
			else if (len > precision)
				len = precision;

			if (!(flags & LEFT)) /* 左对齐，如果对齐长度大于字符串长度，在左边补充空格 */
				while (len < field_width--)
					*str++ = ' ';
			for (i = 0; i < len; ++i) /* 填充字符串 */
				*str++ = *s++;
			while (len < field_width--) /* 处理右对齐，在右边补充空格 */
				*str++ = ' ';
			break;

		case 'o':
			/* 处理8进制数字 */
			str = number(str, va_arg(args, unsigned long), 8,
				field_width, precision, flags);
			break;

		case 'p':
			/* 处理指针打印 */
			if (field_width == -1) {
				field_width = 8; /* 默认宽度是8字节 */
				flags |= ZEROPAD; /* 且默认是按照右对齐在左边填充0 */
			}
			/* 指针是按照16进制显示的 */
			str = number(str,
				(unsigned long) va_arg(args, void *), 16,
				field_width, precision, flags);
			break;

		case 'x':
			/* 16进制整形的小写前导 */
			flags |= SMALL;
		case 'X':
			/* 16进制整形的大写前导 */
			str = number(str, va_arg(args, unsigned long), 16,
				field_width, precision, flags);
			break;

		case 'd': /* d和i都是按照有符号10进制数输出 */
		case 'i':
			flags |= SIGN;
		case 'u': /* u按照无符号10进制输出 */
			str = number(str, va_arg(args, unsigned long), 10,
				field_width, precision, flags);
			break;

		case 'n':
			ip = va_arg(args, int *);
			*ip = (str - buf); /* 这个就不是显示了，把长度写入到 %n 对应的参数指向的位置 */
			break;

		default:
			if (*fmt != '%')
				/* 如果是不支持的模式，且不是下一个 % 区间，则要把前一个 % 补充回来显示 */
				*str++ = '%';
			if (*fmt)
				*str++ = *fmt; /* 不支持的模式，直接显示字符 */
			else
				--fmt; /* 如果是结束字符0，那么倒退一个位置，让for循环取 ++fmt 并判断是否退出循环 */
			break;
		}
	}
	*str = '\0'; /* 字符结尾补0，表示结束 */
	return str-buf; /* 返回显示字符的长度 */
}